/**
 *  EKology - An Ecosystem Simulation Framework
 *  Copyright (C) 2008  Aarón Tavío - aaron.tavio at gmail.com - ULPGC
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package net.ekology.core.gui;

import net.ekology.core.Fecade;
import net.ekology.ekosystem.datatypes.EKVector;

import javax.swing.JPanel;
import java.awt.*;
import java.awt.geom.*;


/**
 * @author Aarón Tavío - aaron.tavio at gmail.com
 * @version 1.0.0 - 20081019-1700
 */
public class EKologyViewport extends JPanel {
    private Point2D.Double oCenter;
    private double dZoom;
    private double dRotationAngle; // measured in radians

    private Fecade oFecade;
    
    private RenderingHints oRenderingHints;
    
    private AffineTransform oBaseTransform;
    private AffineTransform oViewportTransform;
    private AffineTransform oCompositeTransform;
            
    
    public EKologyViewport() {
        oFecade = Fecade.getInstance();
        
        oRenderingHints = new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION , RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        oRenderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        oRenderingHints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        oRenderingHints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        oRenderingHints.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        oRenderingHints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        oRenderingHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        oRenderingHints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
        oRenderingHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        
        oBaseTransform = new AffineTransform();
        oViewportTransform = new AffineTransform();
        oCompositeTransform = new AffineTransform();

        setBaseTransform();
        reset();
    }
    
    
    public AffineTransform reset() {
        oCenter = new Point2D.Double(0, 0); // in word coordinates
        dZoom = 0.5;
        dRotationAngle = 0;
        
        return updateViewportTransform();
    }
    
    
    public void resize() {
        updateBaseTransform();
    }
    
    
    protected void setBaseTransform() {
        this.oBaseTransform.setTransform(1,0,0,-1,getWidth()/2,getHeight()/2);
    }
    
    
    public void updateBaseTransform() {
        setBaseTransform();
        updateCompositeTransform();
    }
    
    
    public AffineTransform updateViewportTransform() {
        oViewportTransform.setToTranslation(-oCenter.getX(), -oCenter.getY());
        oViewportTransform.translate(oCenter.getX(), oCenter.getY());
        oViewportTransform.scale(dZoom, dZoom);
        oViewportTransform.translate(-oCenter.getX(), -oCenter.getY());
        oViewportTransform.rotate(dRotationAngle,oCenter.getX(),oCenter.getY());
        
        updateCompositeTransform();
        
        return oViewportTransform;
    }
    
    
    private void updateCompositeTransform() {
        oCompositeTransform.setTransform(oBaseTransform);
        oCompositeTransform.concatenate(oViewportTransform);
        
        repaint();
    }
    
    
    public AffineTransform getBaseTransform() {
        return oBaseTransform;
    }
    
    public AffineTransform getViewportTransform() {
        return oViewportTransform;
    }
    
        
    public AffineTransform getCompositeTransform() {
        return oCompositeTransform;
    }

    
    public AffineTransform getTransform() {
        return getCompositeTransform();
    }
    
    
    public AffineTransform setPosition(double dXDisp, double dYDisp) {
        return translate(oCenter.getX()-dXDisp,oCenter.getY()-dYDisp);
    }
    
    
    public AffineTransform translate(double dXDisp, double dYDisp) {
        Point2D.Double oDisp;
        
        oDisp = new Point2D.Double(dXDisp,dYDisp);
        
        AffineTransform.getRotateInstance(-dRotationAngle).transform(oDisp, oDisp); // Transforms world movement coordinates into viewport coordinates
        
        oCenter.x -= oDisp.getX()/dZoom;
        oCenter.y -= oDisp.getY()/dZoom;
                
        return updateViewportTransform();
    }
    
    
    public AffineTransform setRotation(double dAngle) {
        return rotate(dAngle-this.dRotationAngle);
    }
    
    /**
     * Rotates the viewport
     * 
     * @param dAngle - measured in degrees
     * @return the viewport's <code>AffineTransform</code>
     */
    public AffineTransform rotate(double dAngle) {
        this.dRotationAngle -= Math.toRadians(dAngle);
        
        return updateViewportTransform();
    }
    
    
    public AffineTransform setZoom(double dZoomUnits) {
        return zoom(dZoomUnits-this.dZoom);
    }
    
    
    public AffineTransform zoom(double dZoomUnits) {
        double dNextZoom;
        
        dNextZoom = (dZoom < 1)? dZoom + (dZoomUnits*dZoom) : dZoom + dZoomUnits;
        
        if (dNextZoom <= 0) dNextZoom = Double.MIN_VALUE;
        
        dZoom = dNextZoom;
        return updateViewportTransform();
    }
    
    
    private void drawCenterAxis(Graphics2D g2) {
        Line2D.Double oLine;
        int iAxisLength = 5;
        double dXCenter,dYCenter;
        
        dXCenter = this.getWidth()/2;
        dYCenter = this.getHeight()/2;
        
        g2.setStroke(new BasicStroke(1f));
        oLine = new Line2D.Double(dXCenter,dYCenter,dXCenter,dYCenter-iAxisLength);
        g2.setColor(Color.RED);
        g2.draw(oLine);
        
        oLine = new Line2D.Double(dXCenter,dYCenter,dXCenter+iAxisLength,dYCenter);
        g2.setColor(Color.GREEN);
        g2.draw(oLine);
    }

    
    public Point2D.Double getDisplacement() {
        return oCenter;
    }
    
    public double getRotation() {
        return dRotationAngle;
    }
    
    
    public double getZoom() {
        return dZoom;
    }
    
    
    /**
     * Transforms a vector from device's coordinates to user's coordinates
     * 
     * @param oVector
     * @return the resulting <code>EKVector</code>
     */
    public EKVector vectorTransoform(EKVector oVector) {
        AffineTransform oTransform;
        Point2D.Double oPoint;

        oTransform = new AffineTransform();
        oPoint = new Point2D.Double(oVector.getComponent(0), oVector.getComponent(1));
        
        oTransform.setToScale(dZoom, dZoom);
        oTransform.rotate(dRotationAngle);

        try {oTransform.inverseTransform(oPoint, oPoint);}
        catch(Exception ignore) {}
        
        return new EKVector(oPoint.getX(),oPoint.getY());
        
    }

    
    @Override
    public void paintComponent(Graphics g) {
        AffineTransform oDefaultTransform;
        Graphics2D g2 = (Graphics2D) g;
        
        g2.setRenderingHints(oRenderingHints);

        super.paintComponent(g);
        oDefaultTransform = g2.getTransform();
        
        g2.transform(getTransform());
                
        oFecade.drawEKosystem(g2);
                
        g2.setTransform(oDefaultTransform);
        
        drawCenterAxis(g2);
    }
        
}